package com.weixiaodong.ModelDesign.生成实例.Singleton模式.双重检验锁;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *  双重检验锁实现单例模式
 *      1. private static volatile SingletonB singletonB 中的volatile关键字的作用(注意：如果没有使用volatile关键字，下面的代码是无法保证单例的)
 *         volatile的两大作用：
 *              1.  防止指令重排
 *              2.  保证线程间对使用该关键字修饰的资源的可见行
 *
 *         1. 所谓的指令重排序是指：对于一个.java文件，我们通过javac指令将其编译为.class文件，就是我们常说的字节码文件，然后jvm通过类加载器
 *            将其加载到jvm中，最终转化为对应的cpu指令，cpu执行这些指令，但是在jvm层，本身可能对指令的一个执行顺序进行一个优化，导致最终的指
 *            令执行顺序并不是代码中从上到下的执行顺序，导致程序的运行结果可能并不是预期结果
 *
 *         2. 所谓的线程间的可见行，是为了保证线程安全的一个条件，试想一个场景： 假设有一个共享资源a = 1，现在有两个线程A和B分别相对a做自增操
 *            作，假设A对a做了一次自增之后还没有来的及写会主存，此时由于A对a的修改对B是不可见的，此时B看见的a还是原值1，则B对a做一次修改，最终
 *            两个线程分别写会自己修改过的值到主存中，最终主存中的值a = 2，实际上a做了两次自增之后，值应该为3
 *
 *         在单例模式中，volatile关键字的作用主要是防止指令重排序的问题
 *
 *     2. 双重检验锁是如何保证单例的?
 *        首先我们先看一段代码
 *              public static SingletonB getInstance() {
 *                  if (singletonB == null) {
 *                      singletonB = new SingletonB();
 *                  }
 *                  return singletonB;
 *              }
 *        咋一看，这段代码并没有什么问题，似乎也可以保证单例，因为只要singletonB第一次被初始化之后，之后就不会走if判断，而是直接返回
 *        下面我们考虑多线程的场景，假设现在有两个线程A，B都想调用getInstance()方法获取实例对象，假设A先调用，这个时候singleton还是
 *        null，走if判断，如果此时singletonB还没有来得及执行到初始化的代码，即new，此时B又开始调用，也走到if判断，虽然A执行了if，但是
 *        没有执行到初始化，所以对于B而言，singleton仍然是null，最终就会执行两次new操作，创建两个不同的对象，从而单例就被破坏了。
 *
 *        怎么解决上述的问题呢? 一个似乎有效的方式：既然上述的问题是由多线程环境破坏的，那么我们就可以通过枷锁解决，就有如下的代码
 *              public static SingletonB getInstance() {
 *                  synchronized (SingletonB.class) {
 *                      if (singletonB == null) {
 *                          singletonB = new SingletonB();
 *                      }
 *                      return singletonB;
 *                  }
 *              }
 *
 *        这段代码是否可以保证单例呢? 是可以的，因为每次只允许一个线程去创建对象，一旦创建成功之后就不会走if判断，但是这种写法非常消耗性能
 *        还是考虑多线程环境，上述的代码和双重检验锁的写法只相差最外层的if判断，外面这层判断的含义是什么呢? 假设A先调用了getInstance方法
 *        此时会上锁，上锁之后会判断，此时为null，创建singletonB对象，然后返回，此时B开始调用getInstance，同样，先上锁，发现不为空，直接
 *        返回，这就是外层的if的作用，即减少上锁检验的性能和时间的消耗，因为没有外层的if判断，每次一旦有线程执行该方法，都会上锁 + 检验 + 释放锁
 *        然而对于上锁和释放锁这样的操作本身就非常消耗性能，所以在外层多设置一个if判断，一旦初始化之后，就没有必要在去上锁检验，直接返回单例对象即可
 *
 *        最终就有了下面的volatile + double check lock实现单例的方式
 *
 *        同样，这种返回会被反射和序列化破坏
 */
public class SingletonB {

    private static volatile SingletonB singletonB;

    private SingletonB() {};


    public static SingletonB getInstance() {
        synchronized (SingletonB.class) {
            if (singletonB == null) {
                singletonB = new SingletonB();
            }
            return singletonB;
        }
    }



    /*public static SingletonB getInstance() {
        // 外层判断空
        if (singletonB == null) {
            // 锁
            synchronized (SingletonB.class) {
                // 内层判断空
                if (singletonB == null) {
                    singletonB = new SingletonB();
                    return singletonB;
                }
            }
        }
        return singletonB;
    }*/

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                SingletonB instance = SingletonB.getInstance();
                System.out.println(instance);
            }
        });


        ExecutorService executorService = Executors.newFixedThreadPool(1000);

        for (int i = 0; i < 1000; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    SingletonB instance = SingletonB.getInstance();
                    System.out.println(instance);
                }
            });
        }
    }
}
